package com.teamfractal.fracdustry.common.blockentity.Generators;

import com.google.common.collect.Queues;
import com.mojang.datafixers.DSL;
import com.teamfractal.fracdustry.common.block.impl.generators.FDKineticGeneratorBlock;
import com.teamfractal.fracdustry.common.container.Generators.FDKineticGeneratorContainer;
import com.teamfractal.fracdustry.common.container.datasync.SimpleArray;
import com.teamfractal.fracdustry.common.util.energystorage.FDEnergyStorage;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.TextComponent;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.energy.CapabilityEnergy;
import net.minecraftforge.event.RegistryEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.registries.ObjectHolder;
import software.bernie.geckolib3.core.IAnimatable;
import software.bernie.geckolib3.core.PlayState;
import software.bernie.geckolib3.core.builder.AnimationBuilder;
import software.bernie.geckolib3.core.controller.AnimationController;
import software.bernie.geckolib3.core.event.predicate.AnimationEvent;
import software.bernie.geckolib3.core.manager.AnimationData;
import software.bernie.geckolib3.core.manager.AnimationFactory;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Queue;
import java.util.concurrent.atomic.AtomicInteger;

@Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD)
public class FDKineticGeneratorBlockEntity extends BlockEntity implements MenuProvider, IAnimatable {
    public static final String NAME = "fracdustry:kinetic_generator";
    private final AnimationFactory factory = new AnimationFactory(this);

    @ObjectHolder(NAME)
    public static BlockEntityType<FDKineticGeneratorBlockEntity> BLOCK_ENTITY_TYPE;

    private final FDEnergyStorage energyStorage = createEnergy();

    private final LazyOptional<FDEnergyStorage> energy = LazyOptional.of(() -> energyStorage);

    private final SimpleArray currentEnergy = new SimpleArray();
    private final SimpleArray maxEnergy = new SimpleArray();

    @SubscribeEvent
    public static void onRegisterBlockEntityType(@Nonnull RegistryEvent.Register<BlockEntityType<?>> event) {
        event.getRegistry().register(BlockEntityType.Builder.
                of(FDKineticGeneratorBlockEntity::new, FDKineticGeneratorBlock.BLOCK).build(DSL.remainderType()).setRegistryName(NAME));
    }

    @Nonnull
    @Override
    public Component getDisplayName() {
        return new TextComponent("");
    }

    public AbstractContainerMenu createMenu(int windowId, @Nonnull Inventory playerInventory, @Nonnull Player playerEntity) {

        if (level != null) {
            return new FDKineticGeneratorContainer(windowId, level, worldPosition, playerInventory, playerEntity, currentEnergy, maxEnergy);
        }
        return null;
    }

    //Disable capabilities when block is removed
    @Override
    public void setRemoved() {
        super.setRemoved();
        energy.invalidate();
    }
    //Block's energy generation and sound playing

    public void tickServer(BlockState state) {
        BlockState blockState = null;
        if (level != null) {
            blockState = level.getBlockState(worldPosition);
        }
        if (blockState != null && (!getBlockState().getValue(BlockStateProperties.POWERED))) {
            level.setBlock(worldPosition, blockState.setValue(BlockStateProperties.POWERED, true),
                    3);
        }
        setChanged();
        sendOutPower();
    }

    private <E extends BlockEntity & IAnimatable> PlayState predicate(AnimationEvent<E> event) {
        event.getController().transitionLengthTicks = 0;
        event.getController().setAnimation(new AnimationBuilder().addAnimation("animation.kinetic_generator.spin", true));
        return  PlayState.CONTINUE;
        //getBlockState().getValue(BlockStateProperties.POWERED) ?   : PlayState.STOP
    }

    //Sending energy to neighbor blocks
    private final Queue<Direction> directionQueue = Queues.newArrayDeque(Direction.Plane.HORIZONTAL);
    private void sendOutPower() {
        AtomicInteger capacity = new AtomicInteger(energyStorage.getEnergyStored());
        if (capacity.get() > 0) {
            this.directionQueue.offer(this.directionQueue.remove());
            for (Direction direction : this.directionQueue) {
                BlockEntity te = null;
                if (level != null
                        && (direction == this.getBlockState().getValue(BlockStateProperties.HORIZONTAL_FACING).getOpposite()
                        || direction == this.getBlockState().getValue(BlockStateProperties.HORIZONTAL_FACING).getCounterClockWise())
                ) {
                    te = level.getBlockEntity(worldPosition.relative(direction));
                }
                if (te != null) {
                    boolean doContinue = te.getCapability(CapabilityEnergy.ENERGY, direction.getOpposite()).map(handler ->
                            {if (handler.canReceive()) {
                                int received = handler.receiveEnergy(Math.min(capacity.get(), 100), false);
                                capacity.addAndGet(-received);
                                energyStorage.consumeEnergy(received);
                                setChanged();
                                return capacity.get() > 0;
                            } else {
                                return true;
                            }
                            }
                    ).orElse(true);
                    if (!doContinue) {
                        return;
                    }
                }
            }
        }
        currentEnergy.set(0,energyStorage.getEnergyStored());
        maxEnergy.set(0,energyStorage.getMaxEnergyStored());
    }

    public FDKineticGeneratorBlockEntity(BlockPos pos, BlockState state)
    {
        super(BLOCK_ENTITY_TYPE,pos,state);
    }

    @Nonnull
    @Override
    public CompoundTag save(@Nonnull  CompoundTag tag) {
        energy.ifPresent(h -> h.serializeNBT(tag));

        return super.save(tag);
    }

    @Override
    public void load(@Nonnull CompoundTag tag) {
        energy.ifPresent(h -> h.deserializeNBT(tag));
        super.load(tag);
    }


    private FDEnergyStorage createEnergy() {
        return new FDEnergyStorage(2500, 2500) {
            @Override
            protected void onEnergyChanged() {
                setChanged();
            }

            @Override
            public boolean canExtract(){
                return true;
            }
        };
    }

    public void manuallyGenerateEnergy(@Nullable BlockEntity blockentity, BlockPos pos){
        if (blockentity instanceof FDKineticGeneratorBlockEntity && pos == this.worldPosition){

        }
    }

    @Nonnull
    @Override
    public <T> LazyOptional<T> getCapability(@Nonnull Capability<T> cap, @Nullable Direction side) {
        if (cap == CapabilityEnergy.ENERGY) {
            boolean isValid = (side != null && side.getAxis().isHorizontal()
                    && (side == this.getBlockState().getValue(BlockStateProperties.HORIZONTAL_FACING).getOpposite()
                    || side == this.getBlockState().getValue(BlockStateProperties.HORIZONTAL_FACING).getCounterClockWise()))
                    || side == Direction.UP;
            return isValid?energy.cast():super.getCapability(cap, side);
        }
        return super.getCapability(cap, side);
    }


    @Override
    public void registerControllers(AnimationData data) {
        data.addAnimationController(new AnimationController(this, "controller", 0, this::predicate));
    }

    @Override
    public AnimationFactory getFactory() {
        return this.factory;
    }
}
